/**
 * Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "OpenGlUtils.h"
#include "napi_tool.h"
#include <EGL/egl.h>

GLuint OpenGlUtils::NO_TEXTURE = 0;
GLuint OpenGlUtils::NO_FBO = 0;

int OpenGlUtils::loadTexture(OhosPixelMapInfos *pixelMap, int usedTexId)
{
    return loadTexture(pixelMap, usedTexId, true);
}

int OpenGlUtils::loadTexture(OhosPixelMapInfos *pixelMap, int usedTexId,
                             bool recycle)
{
    GLuint textures = 0;
    if (usedTexId == NO_TEXTURE) {
        glGenTextures(1, &textures);
        glBindTexture(GL_TEXTURE_2D, textures);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        texImage2D(GL_TEXTURE_2D, 0, pixelMap, 0);
    } else {
        glBindTexture(GL_TEXTURE_2D, usedTexId);
        texSubImage2D(GL_TEXTURE_2D, 0, 0, 0, pixelMap);
        textures = usedTexId;
    }
    if (recycle) {
    }
    return textures;
}

int OpenGlUtils::loadTexture(void *data, int width, int height, int usedTexId)
{
    GLuint textures = 0;
    if (usedTexId == NO_TEXTURE) {
        glGenTextures(1, &textures);
        glBindTexture(GL_TEXTURE_2D, textures);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, data);
    } else {
        glBindTexture(GL_TEXTURE_2D, usedTexId);
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA,
                        GL_UNSIGNED_BYTE, data);
        textures = usedTexId;
    }
    return textures;
}

int OpenGlUtils::loadShader(const std::string &strSource, const int iType)
{
    int compiled = 0;
    int iShader = glCreateShader(iType);
    const char *c_strSource = strSource.c_str();
    glShaderSource(iShader, 1, &c_strSource, nullptr);
    glCompileShader(iShader);
    glGetShaderiv(iShader, GL_COMPILE_STATUS, &compiled);
    if (compiled == 0) {
        char infoLog[GL_LOG_LENGTH];
        glGetShaderInfoLog(iShader, GL_LOG_LENGTH, nullptr, infoLog);
        LOGE("Load Shader Failed, Compilation:%{public}s", infoLog);
        return 0;
    }
    return iShader;
}

int OpenGlUtils::loadProgram(const std::string &strVSource,
                             const std::string &strFSource)
{
    int linked = 0;
    int iVShader = loadShader(strVSource, GL_VERTEX_SHADER);
    if (iVShader == 0) {
        LOGE("Load Program Vertex Shader Failed");
    }
    int iFShader = loadShader(strFSource, GL_FRAGMENT_SHADER);
    if (iFShader == 0) {
        LOGE("Load Program Fragment Shader Failed");
    }
    int iProgId = glCreateProgram();
    GLenum error = glGetError();
    if (error != GL_NO_ERROR) {
        LOGE("glCreateProgram error: %{public}d", error);
    }
    glAttachShader(iProgId, iVShader);
    glAttachShader(iProgId, iFShader);
    glLinkProgram(iProgId);
    glGetProgramiv(iProgId, GL_LINK_STATUS, &linked);
    if (linked <= 0) {
        LOGE("Load Program Linking Failed");
        return 0;
    }
    glDeleteShader(iVShader);
    glDeleteShader(iFShader);
    return iProgId;
}

void OpenGlUtils::texImage2D(int target, int level, OhosPixelMapInfos *pixelMap,
                             int border) {}

void OpenGlUtils::texSubImage2D(int target, int level, int xoffset, int yoffset,
                                OhosPixelMapInfos *pixelMap) {}

void OpenGlUtils::orthoMatrix(float *m, int mOffset, float left, float right,
                              float bottom, float top, float near, float far)
{
    if (m == nullptr)
        return;

    // 应用偏移量
    m += mOffset;
    m[Number::ZEROTH] = 2.0f / (right - left);
    m[Number::FIFTH] = 2.0f / (top - bottom);
    m[Number::TENTH] = -2.0f / (far - near);
    m[Number::TWELFTH] = -(right + left) / (right - left);
    m[Number::THIRTEENTH] = -(top + bottom) / (top - bottom);
    m[Number::FOURTEENTH] = -(far + near) / (far - near);

    // 其他元素设置为0或1以形成正确的正交矩阵
    m[Number::FIRST] = m[Number::SECOND] = m[Number::THIRD] = m[Number::FOURTH] =
        m[Number::SIXTH] = m[Number::SEVENTH] = m[Number::EIGHTH] =
            m[Number::NINTH] = m[Number::ELEVENTH] = 0.0f;
    m[Number::FIFTEENTH] = 1.0f;
}

void OpenGlUtils::setIdentityMatrix(float *sm, int smOffset)
{
    if (sm == nullptr)
        return;

    // 应用偏移量
    float *matrix = sm + smOffset;

    uint8_t matrixSize = 16;
    // 初始化为单位矩阵
    for (int i = 0; i < matrixSize; ++i) {
        matrix[i] = (i % Number::FIFTH == 0) ? 1.0f : 0.0f;
    }
}

std::string OpenGlUtils::getEglErrorString()
{
    EGLint error = eglGetError();
    switch (error) {
        case EGL_SUCCESS:
            return "EGL_SUCCESS";
        case EGL_NOT_INITIALIZED:
            return "EGL_NOT_INITIALIZED";
        case EGL_BAD_ACCESS:
            return "EGL_BAD_ACCESS";
        case EGL_BAD_ALLOC:
            return "EGL_BAD_ALLOC";
        case EGL_BAD_ATTRIBUTE:
            return "EGL_BAD_ATTRIBUTE";
        case EGL_BAD_CONTEXT:
            return "EGL_BAD_CONTEXT";
        case EGL_BAD_CONFIG:
            return "EGL_BAD_CONFIG";
        case EGL_BAD_CURRENT_SURFACE:
            return "EGL_BAD_CURRENT_SURFACE";
        case EGL_BAD_DISPLAY:
            return "EGL_BAD_DISPLAY";
        case EGL_BAD_SURFACE:
            return "EGL_BAD_SURFACE";
        case EGL_BAD_MATCH:
            return "EGL_BAD_MATCH";
        case EGL_BAD_PARAMETER:
            return "EGL_BAD_PARAMETER";
        case EGL_BAD_NATIVE_PIXMAP:
            return "EGL_BAD_NATIVE_PIXMAP";
        case EGL_BAD_NATIVE_WINDOW:
            return "EGL_BAD_NATIVE_WINDOW";
        case EGL_CONTEXT_LOST:
            return "EGL_CONTEXT_LOST";
        default:
            return "Unknown error";
    }
}

void OpenGlUtils::ConvertRGBAtoBGRA(void *buffer, size_t numPixels)
{
    uint32_t *rgba = (uint32_t *)buffer;
    uint8_t pixelBits = 16;
    for (int i = 0; i < numPixels; i++) {
        rgba[i] = (rgba[i] & 0xFF000000) | ((rgba[i] & 0x00FF0000) >> pixelBits) |
                  (rgba[i] & 0x0000FF00) | ((rgba[i] & 0x000000FF) << pixelBits);
    }
}

void OpenGlUtils::SaveRaw(const std::string &path, uint32_t frameid,
                          unsigned char *rgbbuf, int width, int height)
{
    char buf[FILENAME_MAX];
    memset(buf, 0, sizeof(buf));
    snprintf(buf, sizeof(buf), "frame_%dX%d_%d.bin", width, height, frameid);
    std::string file_path = path + "/" + buf;
    FILE *file = NULL;
    file = fopen(file_path.c_str(), "a");
    if (file == NULL) {
        LOGE("%{public}s[%{public}d] error fopen %{public}s", __FUNCTION__,
             __LINE__, file_path.c_str());
        return;
    }
    size_t result = fwrite(rgbbuf, 1, width * height * 4, file);
    if (fclose(file)) {
        LOGE("%{public}s[%{public}d] error fclose %{public}s", __FUNCTION__,
             __LINE__, file_path.c_str());
    }
}